package com.swak.job.timer;

import java.time.LocalDateTime;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.support.CronExpression;

import com.swak.job.worker.Worker;
import com.swak.utils.time.DateTimes;

import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

/**
 * 定时任务
 * 
 * @author DELL
 */
public class TimerTask implements Runnable {
	protected Logger logger = LoggerFactory.getLogger(Worker.class);
	private AtomicBoolean running = new AtomicBoolean(false);
	private volatile LocalDateTime nextDate;
	private final String name;
	private final String cronExpression;
	private final Function<TaskFrag, Object> func;
	private final CronExpression cronSequenceGenerator;

	public TimerTask(String name, String cronExpression) {
		this(name, cronExpression, false, null);
	}

	public TimerTask(String name, String cronExpression, boolean runObBoot, Function<TaskFrag, Object> func) {
		this.name = name;
		this.cronExpression = cronExpression;
		this.func = func;
		this.cronSequenceGenerator = CronExpression.parse(this.cronExpression);
		this.nextDate = runObBoot ? null : this.cronSequenceGenerator.next(DateTimes.now());
	}

	@Override
	public void run() {
		// 尝试获取任务
		if (running.compareAndSet(false, true)) {

			// 发起调度（也可能同步執行）
			this.runTask().whenComplete((frag, ex) -> {

				// 之后后处理
				this.postTask(frag, ex);

				// 修改为可执行
				running.set(false);
			});

			// 调度线程结束
			return;
		}

		// 调试模式：检查调度任务的执行状态，如果长时间处理执行状态则可以遇到问题
		if (logger.isDebugEnabled()) {
			logger.debug("Warn: Job {} is running", this.name);
		}
	}

	/**
	 * 执行任务
	 */
	@SuppressWarnings("unchecked")
	private CompletableFuture<TaskFrag> runTask() {

		CompletableFuture<TaskFrag> future = new CompletableFuture<>();

		// 运行片段
		TaskFrag frag = new TaskFrag();

		// 是否需要执行
		if (!(nextDate == null || nextDate.isBefore(frag.runTime))) {
			future.complete(frag.setSuccess(false));
			return future;
		}

		// 设置预计下次需要执行的时间
		frag.setNextTime(cronSequenceGenerator.next(frag.getRunTime()));

		// 执行任务 - 可以返回异步结果
		Object result = null;
		Throwable ex = null;
		try {
			result = this.doDispatch(frag);
		} catch (Exception e) {
			ex = e;
		}

		// 执行任务
		if (result != null && result instanceof CompletionStage) {
			CompletionStage<Object> resultFuture = (CompletionStage<Object>) result;
			resultFuture.whenComplete((v, e) -> {
				if (e != null) {
					future.completeExceptionally(e);
				} else {
					future.complete(frag.setSuccess(true));
				}
			});
		} else if (ex != null) {
			future.completeExceptionally(ex);
		} else {
			future.complete(frag.setSuccess(true));
		}
		return future;
	}

	/**
	 * 执行任务之后
	 */
	private void postTask(TaskFrag frag, Throwable ex) {
		try {
			/**
			 * 异常处理
			 */
			if (ex != null) {
				logger.error("Job {} Dispatch Error.", this.name, ex);
				return;
			}

			/**
			 * 调整下次执行时间
			 */
			// 执行成功才需要调整下次执行时间
			if (frag == null || !frag.isSuccess()) {
				return;
			}

			// 如果已经在当前时间之后
			nextDate = frag.getNextTime();
			LocalDateTime now = DateTimes.now();
			if (nextDate.isBefore(now)) {
				nextDate = cronSequenceGenerator.next(now);
			}

			/**
			 * 调试模式
			 */
			if (logger.isDebugEnabled() && frag.isSuccess()) {
				logger.debug("Job {} Dispatch {}, Next Dispatch Time is {}", this.name,
						frag.isSuccess() ? "success" : "failure",
						nextDate != null ? DateTimes.getFormatTime(nextDate, "yyyy-MM-dd HH:mm:ss") : "调度中...");
			}
		} catch (Exception e) {
			logger.error("任务执行结束后：", e);
		}
	}

	/**
	 * 执行调度
	 * 
	 * @param frag
	 * @return
	 */
	protected Object doDispatch(TaskFrag frag) {
		return this.func != null ? this.func.apply(frag) : null;
	}

	/**
	 * 
	 * 任务片段
	 * 
	 * @author lifeng
	 */
	@Getter
	@Setter
	@Accessors(chain = true)
	public static class TaskFrag {

		/**
		 * 调度时间
		 */
		LocalDateTime runTime = LocalDateTime.now();

		/**
		 * 调度结果
		 */
		boolean success = Boolean.TRUE;

		/**
		 * 预计下次执行时间(需要调度才设置，不需要则不设置)
		 */
		LocalDateTime nextTime = null;
	}

}
